package org.clockworkmages.games.anno1186.situations.combat;

import java.util.List;
import java.util.Map;

import org.clockworkmages.games.anno1186.CombatUtil;
import org.clockworkmages.games.anno1186.GameBeansContext;
import org.clockworkmages.games.anno1186.GameCharacterService;
import org.clockworkmages.games.anno1186.GameDataService;
import org.clockworkmages.games.anno1186.GameGuiService;
import org.clockworkmages.games.anno1186.GameStateService;
import org.clockworkmages.games.anno1186.OptionUtil;
import org.clockworkmages.games.anno1186.TimeConstants;
import org.clockworkmages.games.anno1186.model.character.IncapacitatedState;
import org.clockworkmages.games.anno1186.model.character.NonPlayerCharacter;
import org.clockworkmages.games.anno1186.model.character.PlayerCharacter;
import org.clockworkmages.games.anno1186.model.character.SpecialAttack;
import org.clockworkmages.games.anno1186.model.character.StatConstants;
import org.clockworkmages.games.anno1186.model.common.Pair;
import org.clockworkmages.games.anno1186.model.effect.Condition;
import org.clockworkmages.games.anno1186.model.effect.ConditionConstanst;
import org.clockworkmages.games.anno1186.model.effect.ConditionStatus;
import org.clockworkmages.games.anno1186.model.option.GoBackOption;
import org.clockworkmages.games.anno1186.model.option.Option;
import org.clockworkmages.games.anno1186.model.situation.Situation;
import org.clockworkmages.games.anno1186.scripting.PlayerCharacterScriptingWrapper;
import org.clockworkmages.games.anno1186.scripting.ScriptingService;
import org.clockworkmages.games.anno1186.situations.combat.options.AttackOption;
import org.clockworkmages.games.anno1186.situations.combat.options.FleeOption;
import org.clockworkmages.games.anno1186.situations.combat.options.ResistIncapacitatedStateOption;
import org.clockworkmages.games.anno1186.situations.combat.options.SurrenderOption;
import org.clockworkmages.games.anno1186.text.TextUtil;

public class CombatSituation
		extends Situation {

	// private boolean initiated;

	private CombatStatus combatStatus;

	private boolean playerConsumedCombatAction = false;

	/**
	 * IncapacitatedState's ID and the difficulty of resisting/overcoming it.
	 */
	private Pair<String, Integer> incapacitatedState = null;

	private NonPlayerCharacter enemy;
	private PlayerCharacter me;

	private GameCharacterService gameCharacterService;
	private GameStateService gameStateService;
	private GameDataService gameDataService;
	private GameGuiService gameGuiService;
	private ScriptingService scriptingService;
	private PlayerCharacterScriptingWrapper playerCharacterScriptingWrapper;

	public CombatSituation(NonPlayerCharacter enemy) {
		gameCharacterService = GameBeansContext
				.getBean(GameCharacterService.class);
		playerCharacterScriptingWrapper = GameBeansContext
				.getBean(PlayerCharacterScriptingWrapper.class);
		gameStateService = GameBeansContext.getBean(GameStateService.class);
		gameDataService = GameBeansContext.getBean(GameDataService.class);
		gameGuiService = GameBeansContext.getBean(GameGuiService.class);
		scriptingService = GameBeansContext.getBean(ScriptingService.class);

		this.enemy = enemy;
		this.me = gameStateService.getGameState().getCharacter();
		this.setClearScreen(true);
		combatStatus = CombatStatus.STARTING;
		this.setTitle("You are fighting " + enemy.getName());

	}

	@Override
	public void update() {
		super.update();
		if (combatStatus == CombatStatus.STARTING) {
			gameCharacterService.refreshStats(enemy);
			gameCharacterService.restoreAll(enemy);
			updateText();
			updateOptions();
			combatStatus = CombatStatus.IN_PROGRESS;
		} else if (playerConsumedCombatAction) {
			// TODO:
			// - evaluate player's and enemy's health
			if (enemy.isStatsAreStale()) {
				gameCharacterService.refreshStats(enemy);
			}

			if (combatStatus == CombatStatus.IN_PROGRESS) {
				executeEnemyTurn();
			}
			// - execute enemy turn
			// - evaluate player's and enemy's health
			// - update enemy's stats panel
			// - update available options, taking the
			// incapacitatedStateIdAndDifficulty into account
			updateOptions();
			updateText();
			playerConsumedCombatAction = false;
		}

		checkIfTired();

		double enemyHealthPerc = enemy.getBaseStats().get(StatConstants.HEALTH)
				/ enemy.getDerivates().get(StatConstants.D_MAX_HEALTH);
		gameGuiService
				.getGamePanel()
				.getExplorationPanel()
				.getRightTotemPanel()
				.getTotemImage()
				.setTargetState(enemyHealthPerc > 0 ? enemyHealthPerc : 0,
						2 * TimeConstants.SECOND);

	}

	private void checkIfTired() {
		boolean isTired = this.me.getStamina() < 1;
		boolean wasTired = false;
		for (String conditionId : this.me.getConditions().keySet()) {
			if (ConditionConstanst.TIRED.equals(conditionId)) {
				wasTired = true;
			}
		}
		if (wasTired && !isTired) {
			this.me.getConditions().remove(ConditionConstanst.TIRED);
			gameGuiService.getTextPane().addInfoToBuffer(
					"You no longer feel tired!");
		} else if (!wasTired && isTired) {
			Condition condition = gameDataService.getGameData().getConditions()
					.get(ConditionConstanst.TIRED);
			ConditionStatus conditionStatus = condition.makeStatus(1, -1);
			this.me.getConditions().put(ConditionConstanst.TIRED,
					conditionStatus);
			gameGuiService
					.getTextPane()
					.addInfoToBuffer(
							"You are tired, which makes it harder for you to land succesful hits and to dodge enemy attacks!");
		}
	}

	private void executeEnemyTurn() {
		boolean specialEffectExecuted = false;
		if (!enemy.getSpecialAttacks().isEmpty()) {
			SpecialAttack specialAttack = selectSpecialAttack(enemy
					.getSpecialAttacks());
			if (specialAttack != null) {
				Map<String, Object> enemyModel = scriptingService
						.getDefaultModel(true);
				scriptingService.execute(specialAttack.getEffect(), enemyModel);
				gameGuiService.getTextPane().addInfoToBuffer(
						specialAttack.getText());
				specialEffectExecuted = true;
			}
		}
		if (!specialEffectExecuted) {
			PlayerCharacter defender = gameStateService.getGameState()
					.getCharacter();
			NonPlayerCharacter attacker = gameStateService.getEnemy();
			CombatUtil.performAttack(gameGuiService, defender, attacker);
		}
	}

	private SpecialAttack selectSpecialAttack(List<SpecialAttack> specialAttacks) {

		ScriptingService scriptingService = GameBeansContext
				.getBean(ScriptingService.class);

		for (SpecialAttack specialAttack : specialAttacks) {
			String condition = specialAttack.getCondition();
			if (condition == null || scriptingService.conditionIsMet(condition)) {
				return specialAttack;
			}
		}
		return null;
	}

	private void updateOptions() {
		List<Option> options = this.getOptions();
		this.getOptions().clear();

		if (combatStatus == CombatStatus.FLED) {
			options.add(GoBackOption.INSTANCE);
		} else if (me.getHealth() <= 0
				|| combatStatus == CombatStatus.SURRENDERED) {
			// Defeat : (
			List<Option> availableSubduedOptions = OptionUtil
					.getAvailableOptions(enemy.getSubduedOptions());
			if (availableSubduedOptions.isEmpty()) {
				// no Subdued options, so just present the Defeated options:
				gameGuiService.getTextPane().appendText("You were defeated!\n");
				options.addAll(enemy.getDefeatedOptions());
			} else {
				// there are Subdued options available, present those
				gameGuiService.getTextPane().appendText(

				TextUtil.parse(enemy.getSubduedText()) + "\n");
				options.addAll(enemy.getSubduedOptions());
			}
			removeCombatOnlyConditions();
		} else if (enemy.getHealth() <= 0) {
			// Victory!
			gainExperience();
			List<Option> availableSubduingOptions = OptionUtil
					.getAvailableOptions(enemy.getSubduingOptions());
			if (availableSubduingOptions.isEmpty()) {
				// no Subduing options, so just present the Victorious options:
				gameGuiService.getTextPane()
						.appendText("You are victorious!\n");
				options.addAll(enemy.getDefeatedOptions());
			} else {
				// there are Subduing options available, present those
				gameGuiService.getTextPane().appendText(
						TextUtil.parse(enemy.getSubduingText()) + "\n");
				options.addAll(enemy.getSubduingOptions());
			}
			removeCombatOnlyConditions();
		} else if (this.incapacitatedState != null) {
			IncapacitatedState incapacitatedState = IncapacitatedState
					.valueOf(this.incapacitatedState.getFirst());
			options.add(new ResistIncapacitatedStateOption(incapacitatedState,
					this.incapacitatedState.getSecond(), this));
		} else {
			AttackOption attackOption = new AttackOption();
			options.add(attackOption);

			FleeOption fleeOption = new FleeOption();
			options.add(fleeOption);

			List<Option> availableSubduedOptions = OptionUtil
					.getAvailableOptions(enemy.getSubduedOptions());
			if (!availableSubduedOptions.isEmpty()) {
				SurrenderOption surrenderOption = new SurrenderOption();
				options.add(surrenderOption);
			}
		}

		if (me.getHealth() < 1) {
			me.setHealth(1);
		}
		if (me.getStamina() < 0) {
			me.setStamina(0);
		}
	}

	private void gainExperience() {
		int experienceGained = (int) Math.ceil(Double.valueOf(enemy.getLevel())
				/ Double.valueOf(me.getLevel()) * 10);
		playerCharacterScriptingWrapper.gainExperience(experienceGained);
	}

	private void removeCombatOnlyConditions() {
		Map<String, Condition> conditions = gameDataService.getGameData()
				.getConditions();
		for (String conditionId : me.getConditions().keySet()) {
			Condition condition = conditions.get(conditionId);
			if (condition.isCombatOnly()) {
				me.getConditions().remove(conditionId);
			}
		}
	}

	private void updateText() {
		String pronoun = TextUtil.capitalPronoun(enemy.getGender());
		double dHealthStatus = enemy.getBaseStats().get(StatConstants.HEALTH)
				/ enemy.getDerivates().get(StatConstants.D_MAX_HEALTH);
		String healthStatus;
		if (dHealthStatus > 0.99) {
			healthStatus = "at full health";
		} else if (dHealthStatus > 0.8) {
			healthStatus = "lightly wounded";
		} else if (dHealthStatus > 0.6) {
			healthStatus = "wounded";
		} else if (dHealthStatus > 0.3) {
			healthStatus = "seriously wounded";
		} else if (dHealthStatus > 0.2) {
			healthStatus = "very badly wounded";
		} else {
			healthStatus = "close to dying wounded";
		}
		this.setText(enemy.getDescription() + "\n" + pronoun + " is "
				+ healthStatus + ".");
	}

	public boolean isPlayerConsumedCombatAction() {
		return playerConsumedCombatAction;
	}

	public void setPlayerConsumedCombatAction(boolean playerConsumedCombatAction) {
		this.playerConsumedCombatAction = playerConsumedCombatAction;
	}

	public NonPlayerCharacter getEnemy() {
		return enemy;
	}

	public Pair<String, Integer> getIncapacitatedState() {
		return incapacitatedState;
	}

	public void setIncapacitatedState(Pair<String, Integer> incapaciatedState) {
		this.incapacitatedState = incapaciatedState;
	}

	public void setCombatStatus(CombatStatus combatStatus) {
		this.combatStatus = combatStatus;
	}

}
